package pl.drypisy;

import java.awt.Rectangle;
import java.awt.image.BufferedImage;

/**
 * Created with IntelliJ IDEA.
 * User: adam
 * Date: 09.01.13
 * Time: 12:17
 * This image processor is used to map points in original image to points on destination image. The new coordinates are
 * calculated by first setting the four corners of new image.
 */
public class PerspectiveProcess extends ImageProcess {
    private int x0,y0,x1,y1,x2,y2,x3,y3;
    private double dx1, dy1, dx2, dy2, dx3, dy3;

    /**
     * Projection matrix values
     */
    private double A, B, C, D, E, F, G, H, I;
    private Rectangle transformedSpace;
    private BufferedImage src, dst;

    public void setCorners(int x0, int y0, //upper-left
                           int x1, int y1, //upper-right
                           int x2, int y2, //bottom-left
                           int x3, int y3 //bottom-right
                          )
    {
        this.x0 = x0;
        this.y0 = y0;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        this.x3 = x3;
        this.y3 = y3;
    }

    /**
     * Linear algebra point transformation algorithm + drawing.
     * @param img
     * @return
     */
    @Override
    public BufferedImage process(BufferedImage img) {
        src = img;

        /**
         * Mapping unit square to transformed coordinates. As an effect we get point (0,0) mapped to (dx0,dy0), (1,0) to
         * (dx1,dy1) etc,
         */
        dx1 = x1-x2;
        dy1 = y1-y2;
        dx2 = x3-x2;
        dy2 = y3-y2;
        dx3 = x0-x1+x2-x3;
        dy3 = y0-y1+y2-y3;

        /**
         * 3x3 matrix. a33 is not needed, since it's not used anywhere.
         */
        double a11, a12, a13, a21, a22, a23, a31, a32;

        /**
         * Is the mapping affine?
         */
        if (dx3 == 0 && dy3 == 0) {
            a11 = x1-x0;
            a21 = x2-x1;
            a31 = x0;
            a12 = y1-y0;
            a22 = y2-y1;
            a32 = y0;
            a13 = a23 = 0;
        } else {
            a13 = (dx3*dy2-dx2*dy3)/(dx1*dy2-dy1*dx2);
            a23 = (dx1*dy3-dy1*dx3)/(dx1*dy2-dy1*dx2);
            a11 = x1-x0+a13*x1;
            a21 = x3-x0+a23*x3;
            a31 = x0;
            a12 = y1-y0+a13*y1;
            a22 = y3-y0+a23*y3;
            a32 = y0;
        }

        /**
         * Projection matrix values.
         */
        A = a22 - a32*a23;
        B = a31*a23 - a21;
        C = a21*a32 - a31*a22;
        D = a32*a13 - a12;
        E = a11 - a31*a13;
        F = a31*a12 - a11*a32;
        G = a12*a23 - a22*a13;
        H = a21*a13 - a11*a23;
        I = a11*a22 - a21*a12;

        srcWidth = src.getWidth();
        srcHeight = src.getHeight();
        
        transformedSpace = new Rectangle(0,0,srcWidth,srcHeight);
        getTransformedImageRect(transformedSpace);
        int transformedX = transformedSpace.x;
        int transformedY = transformedSpace.y;
        double[] out = new double[2];
        dst = new BufferedImage(transformedSpace.width, transformedSpace.height, BufferedImage.TYPE_INT_ARGB);
        int[] inPixels = src.getRGB(0, 0, srcWidth, srcHeight, null, 0, srcWidth);
        int[] outPixels = new int[transformedSpace.width];

        for (int y = 0; y < dst.getHeight(); y++) {
            for (int x = 0; x < dst.getWidth(); x++) {
                getOriginalCoordinates(transformedX+x, transformedY+y, out);
                int pix;
                int srcX = (int)Math.floor( out[0] );
                int srcY = (int)Math.floor( out[1] );
                if(doInterpolation) {
                    double xWeight = out[0]-srcX;
                    double yWeight = out[1]-srcY;
                    int nw, ne, sw, se;
                    nw = getPixel( inPixels, srcX, srcY, srcWidth, srcHeight);
                    ne = getPixel( inPixels, srcX+1, srcY, srcWidth, srcHeight );
                    sw = getPixel( inPixels, srcX, srcY+1, srcWidth, srcHeight );
                    se = getPixel( inPixels, srcX+1, srcY+1, srcWidth, srcHeight );
                    pix = bilinearInterpolation(xWeight, yWeight, nw, ne, sw, se);
                }
                else pix = getPixel(inPixels,srcX,srcY,srcWidth,srcHeight);
                outPixels[x] = pix;
            }
            dst.setRGB(0, y, transformedSpace.width, 1, outPixels, 0, transformedSpace.width);
        }
        return dst;
    }

    private void getTransformedImageRect(Rectangle rect)
    {
        rect.x = (int)Math.min( Math.min( x0, x1 ), Math.min( x2, x3 ) );
        rect.y = (int)Math.min( Math.min( y0, y1 ), Math.min( y2, y3 ) );
        rect.width = (int)Math.max( Math.max( x0, x1 ), Math.max( x2, x3 ) ) - rect.x;
        rect.height = (int)Math.max( Math.max( y0, y1 ), Math.max( y2, y3 ) ) - rect.y;
    }

    protected void getOriginalCoordinates(int x, int y, double out[])
    {
        out[0] = src.getWidth() * (A*x+B*y+C)/(G*x+H*y+I);
        out[1] = src.getHeight() * (D*x+E*y+F)/(G*x+H*y+I);
    }
}
